package de.osmembrane.tools.integrity_test;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import de.osmembrane.model.xml.XMLFunction;
import de.osmembrane.model.xml.XMLFunctionGroup;
import de.osmembrane.model.xml.XMLOsmosisStructure;
import de.osmembrane.model.xml.XMLParameter;
import de.osmembrane.model.xml.XMLPipe;
import de.osmembrane.model.xml.XMLTask;

public class IntegrityTest {

	private File file;

	private XMLOsmosisStructure xmlOS;

	private Map<String, XMLFunctionGroup> groupIds = new HashMap<String, XMLFunctionGroup>();
	private Map<String, XMLFunction> functionIds = new HashMap<String, XMLFunction>();
	private Map<String, XMLTask> taskNames = new HashMap<String, XMLTask>();

	public IntegrityTest(String filename) throws FileNotFoundException {
		this.file = new File(filename);

		if (!file.isFile()) {
			throw new FileNotFoundException("file not found.");
		}

		if (!file.canRead()) {
			throw new FileNotFoundException("file not readable.");
		}
	}

	public void loadXML() throws JAXBException {

		JAXBContext jc = JAXBContext.newInstance("de.osmembrane.model.xml");

		Unmarshaller u = jc.createUnmarshaller();

		XMLOsmosisStructure otd = (XMLOsmosisStructure) u.unmarshal(file);

		this.xmlOS = otd;
	}

	/**
	 * Checks the integrity of the file.
	 * 
	 * @throws IntegrityException
	 *             if the file consistency is broken.
	 */
	public void checkIntegrity() throws IntegrityException {
		groupIds.clear();
		functionIds.clear();
		taskNames.clear();

		System.out.println("Found XML-file for osmosis version "
				+ xmlOS.getOsmosisVersion());

		for (XMLFunctionGroup group : xmlOS.getFunctionGroup()) {
			if (groupIds.containsKey(group.getId())) {
				throw new IntegrityException("The function group "
						+ group.getFriendlyName() + "tries to use the id \""
						+ group.getId() + "\" which is already used by "
						+ groupIds.get(group.getId()));
			} else {
				groupIds.put(group.getId(), group);
			}

			if (group.getFunction().size() == 0) {
				throw new IntegrityException("The group "
						+ group.getFriendlyName()
						+ " should have at least one function inside.");
			}

			for (XMLFunction function : group.getFunction()) {
				validateFunction(group, function);
			}
		}
	}

	/**
	 * Validates a function.
	 * 
	 * @param group
	 * @param function
	 * @throws IntegrityException
	 */
	private void validateFunction(XMLFunctionGroup group, XMLFunction function)
			throws IntegrityException {
		if (functionIds.containsKey(function.getId())) {
			throw new IntegrityException("The function function "
					+ function.getFriendlyName() + "tries to use the id \""
					+ function.getId() + "\" which is already used by "
					+ functionIds.get(function.getId()));
		} else {
			functionIds.put(function.getId(), function);
		}

		if (function.getTask().size() == 0) {
			throw new IntegrityException("The function "
					+ function.getFriendlyName()
					+ " should have at least one task inside.");
		}

		/* generate the required pipes for each function */
		List<String> lastPipes = null;

		for (XMLTask task : function.getTask()) {
			validateTask(function, task, lastPipes);
		}

	}

	/**
	 * Validate a task.
	 * 
	 * @param function
	 * @param task
	 * @param lastPipes
	 * @throws IntegrityException
	 */
	private void validateTask(XMLFunction function, XMLTask task,
			List<String> lastPipes) throws IntegrityException {

		if (taskNames.containsKey(task.getName())) {
			throw new IntegrityException("The task name " + task.getName()
					+ " is already used by another task.");
		} else {
			taskNames.put(task.getName(), task);
		}

		/* check all pipes */
		List<String> pipes = new ArrayList<String>();

		validatePipes(task, "in", task.getInputPipe(), pipes, lastPipes);
		validatePipes(task, "out", task.getOutputPipe(), pipes, lastPipes);

		lastPipes = pipes;

		validateParameters(task);
	}

	/**
	 * Validates the parameters of a task.
	 * 
	 * @param task
	 * @throws IntegrityException
	 */
	private void validateParameters(XMLTask task) throws IntegrityException {
		List<String> bboxParams = new ArrayList<String>();
		List<String> params = new ArrayList<String>();
		for (XMLParameter parameter : task.getParameter()) {
			if (params.contains(parameter.getName())) {
				throw new IntegrityException("The task \"" + task.getName()
						+ "\" has a parameter \"" + parameter.getName() + "\", " +
						" more than one time defined, but only one time is allowed.");
			}
			
			/* check if it is an bbox param */
			if (parameter.getType() == "bbox") {
				bboxParams.add(parameter.getName());
			}
		}
		
		/* check the bbox problem */
		if (bboxParams.size() > 0) {
			boolean left = false, right = false, top = false, bottom = false;
			for (String param : bboxParams) {
				if (param.equals("left")) {
					left = true;
				}
				if (param.equals("right")) {
					right = true;
				}
				if (param.equals("top")) {
					top = true;
				}
				if (param.equals("bottom")) {
					bottom = true;
				}
			}
			if (!left || !right || !top || !bottom) {
				throw new IntegrityException("The task \"" + task.getName()
						+ "\" has a bbox params, but not all the four "
						+ "required parameters left, top, right and bottom.");
			}
		}
	}

	/**
	 * Validate the given pipes of a task.
	 * 
	 * @param task
	 * @param pipes
	 * @param pipeList
	 * @param lastPipes
	 * @throws IntegrityException
	 */
	private void validatePipes(XMLTask task, String name, List<XMLPipe> pipes,
			List<String> pipeList, List<String> lastPipes)
			throws IntegrityException {
		for (XMLPipe pipe : pipes) {
			String pipeId = name + "." + pipe.getType() + "." + pipe.getIndex();
			if (pipeList.contains(pipeId)) {
				throw new IntegrityException("The task " + task.getName()
						+ " shouldn't have two equivalent pipes, see " + pipeId);
			}
			if (lastPipes != null && !lastPipes.contains(pipeId)) {
				throw new IntegrityException(
						"The task "
								+ task.getName()
								+ " does not have the same in-/out-pipes as the other ones of the function.");
			}

			pipeList.add(pipeId);
		}
	}
}

/**
 * Integrity Exception.
 * 
 * @author jakob_jarosch
 */
class IntegrityException extends Exception {
	private static final long serialVersionUID = -3396714756627965120L;

	public IntegrityException(String message) {
		super(message);
	}
}